/**
 * Lexical Analyzer code
 */
package lexico;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Hashtable;

import semantico.SemanticAnalyzer;
import symbols.*;

/**
 * @author rifiorelli
 * 
 */
public class LexicalAnalyzer {

	public StringBuffer program;
	private Hashtable<String, ReservedWord> palReservada = new Hashtable<String, ReservedWord>();
	public Hashtable<String, ReservedWord> getPalReservada() {
		return palReservada;
	}

	private void setPalReservada(Hashtable<String, ReservedWord> palReservada) {
		this.palReservada = palReservada;
	}

	private String getWordRead() {
		return wordRead;
	}

	private void setWordRead(String wordRead) {
		this.wordRead = wordRead;
	}

	public int lineNumber = 1;
	public String wordRead = "";
	
	public LexicalAnalyzer(String fileName) {
		this.program = new StringBuffer();
		this.setLineNumber(1);
		this.palReservada = new Hashtable<String, ReservedWord>();
		geraPalavrasReservadas();
		
		// Read Programa
		try {
			// abrir e ler o arquivo
			BufferedReader in = new BufferedReader(new FileReader(fileName));
			String line = null;

			while ((line = in.readLine()) != null) {
//				if (line.length() >= 2) {
//					if (!line.substring(0, 2).equals("%%")) {
						program.append(line);
						program.append("\n");
//					}
//				} else {
//					program.append(line);
//					program.append("\n");
//				}
			}
			in.close();
		} catch (IOException e) {
			new ErroLexico("Certifique-se de ter enviado o arquivo correto.", 0);
		}
	}
	
	/**
	 * adiciona o lexema e a palavra reservada na tabela de palavras reservadas
	 * @param wt
	 */
	private void reserve(ReservedWord wt) {
		palReservada.put(wt.lexeme, wt);
	}

	/**
	 * Contém todas as palavras reservadas
	 */
	private void geraPalavrasReservadas() {
		
		reserve(new ReservedWord("program", 	Tag.PROGRAM)); 
		reserve(new ReservedWord("struct", 		Tag.STRUCT)); 
		reserve(new ReservedWord("if", 			Tag.IF)); 
		reserve(new ReservedWord("endif", 		Tag.ENDIF)); 
		reserve(new ReservedWord("else", 		Tag.ELSE));
		reserve(new ReservedWord("endelse", 	Tag.ENDELSE));
		reserve(new ReservedWord("while", 		Tag.WHILE));
		reserve(new ReservedWord("endwhile", 	Tag.ENDWHILE));
		reserve(new ReservedWord("for", 		Tag.FOR));
		reserve(new ReservedWord("endfor", 		Tag.ENDFOR));
		reserve(new ReservedWord("return",		Tag.RETURN));
		reserve(new ReservedWord("print",		Tag.PRINT));
		reserve(new ReservedWord("input",		Tag.INPUT));
		reserve(new ReservedWord("num", 		Tag.NUMBER));
		reserve(new ReservedWord("id", 			Tag.ID));
		reserve(new ReservedWord("%%", 			Tag.COMMENT));
		reserve(new ReservedWord("(", 			Tag.LEFTPAR));
		reserve(new ReservedWord(")", 			Tag.RIGHTPAR));
		reserve(new ReservedWord("{", 			Tag.LCHAVE));
		reserve(new ReservedWord("}", 			Tag.RCHAVE));
		reserve(new ReservedWord("[", 			Tag.LCOLCHETE));
		reserve(new ReservedWord("]", 			Tag.RCOLCHETE));
		reserve(new ReservedWord(",", 			Tag.COLON));
		reserve(new ReservedWord(":", 			Tag.TWOPOINTS));
		reserve(new ReservedWord(";", 			Tag.SEMICOLON));
		reserve(ReservedWord.and);
		reserve(ReservedWord.or);
		reserve(ReservedWord.eq);
		reserve(ReservedWord.ne);
		reserve(ReservedWord.le);
		reserve(ReservedWord.lt);
		reserve(ReservedWord.ge);
		reserve(ReservedWord.gt);
		reserve(ReservedWord.minus);
		reserve(ReservedWord.add);
		reserve(ReservedWord.pow);
		reserve(ReservedWord.mult);
		reserve(ReservedWord.div);
		reserve(ReservedWord.atribuicao);// =
		reserve(ReservedWord.restdiv);// %
		reserve(ReservedWord.True);
		reserve(ReservedWord.False);
		reserve(Type.Int);
		reserve(Type.Bool);
		reserve(Type.Char);
		reserve(Type.Float);
		reserve(Type.Void);
	}

	/**
	 * Recebe a validacao se eh necessario deletar o char lido e devolve o valor
	 * em ASCII do caractere
	 * 
	 * @param delete
	 *            recebe o valor e deleta o char, se necessario
	 * @return char valor em ASCII do caractere
	 */
	private String getNextChar(boolean delete) {
		String str = null;
		if (program.length() > 1) {
			str = program.substring(0, 1);
			if (delete)
				program.delete(0, 1);
		}
		else {
			str = "\0";
		}
		return str; // pega valor ASCII do caractere
	}
	
	private void deleteNextChar() {
		program.delete(0, 1);
	}
	
	/**
	 * Adiciona o tokenLido na tabela de simbolos
	 * @param tokenLido
	 * @return
	 */
	public ReservedWord adicionaTabelaSimbolos(ReservedWord tokenLido) {
		
		return tokenLido;
	}
	
	/**
	 * 
	 * @return
	 */
	public Token getNextToken() {
		String chrRead = "";
		
		chrRead = getNextChar(true);
		wordRead = "";
		
//// verifica se é EOF
//		if (isEOF(chrRead)) {
//			return new Token(Tag.EOF);
//		}
		
// verifica se é nulo
		
		if (chrRead.equals("")) {
			return new Token("",Tag.VAZIO);
		}
		
// verifica se é NEWLINE, TAB ou WHITESPACE
		// adiciona numero da linha se é NEWLINE
		if (getCharValue(chrRead) == Tag.NEWLINE) {
			addLineNumber();// pega o número da linha
		}
		if (isWhitespace(chrRead)) {
			while (isWhitespace(chrRead) && !isEOF(chrRead)) {
				chrRead = getNextChar(true);
				if (getCharValue(chrRead) == Tag.NEWLINE) {
					addLineNumber();// pega o número da linha
				}
			}
		}

// verifica os caracteres especiais e simbolos
		wordRead = chrRead;
		switch (getCharValue(chrRead)) {
		case Tag.EXCL: 			// !
			if (getCharValue(getNextChar(false)) == Tag.EQUAL_SIG) {
				new ErroLexico("Nesta linguagem 'diferente' é feito com <>, e nao com !=", lineNumber);
			}			
		case Tag.CIFRAO: 		// $
		case Tag.SHARP: 		// #
			new ErroLexico("Caratere invalido", getLineNumber());
			
		case Tag.COLON:			// ,
		case Tag.SEMICOLON:		// ;
		case Tag.TWOPOINTS:		// :
			if (isPalavraReservada(wordRead)) {
				return adicionaTabelaSimbolos(palReservada.get(wordRead));
			}
		
			// Pega o resto da divisao e os comentarios
		case Tag.PORCENT: 		// %
			if (getCharValue(getNextChar(false)) == Tag.PORCENT) { // trata comentario
				while (getCharValue(getNextChar(true)) != Tag.NEWLINE && !isEOF(chrRead));
				addLineNumber();// pega o número da linha
				return this.getNextToken(); // ignora e devolve o próximo token
			}
			else {
				if (isPalavraReservada(wordRead)) {
					return adicionaTabelaSimbolos(palReservada.get(wordRead));
				}
			}
			
		case Tag.LEFTPAR:		// (
		case Tag.LCHAVE:		// )
		case Tag.RCHAVE:		// {
		case Tag.RIGHTPAR:		// }
		case Tag.LCOLCHETE: 	// [
		case Tag.RCOLCHETE: 	// ]
			if (isPalavraReservada(wordRead)) {
				return adicionaTabelaSimbolos(palReservada.get(wordRead));
			}

			// Pega o AND
		case Tag.ECOMERCIAL:	// &
			if (getCharValue(getNextChar(false)) == Tag.ECOMERCIAL) {
				wordRead = wordRead + getNextChar(true);
				if (isPalavraReservada(wordRead)) {
					return adicionaTabelaSimbolos(palReservada.get(wordRead));
				}
			}
			else {
				new ErroLexico("Uso incorreto de &", getLineNumber());
			}

			// Pega o OR
		case Tag.PIPE:			// |
			if (getCharValue(getNextChar(false)) == Tag.PIPE) {
				wordRead = wordRead + getNextChar(true);
				if (isPalavraReservada(wordRead)) {
					return adicionaTabelaSimbolos(palReservada.get(wordRead));
				}
			}
			else {
				new ErroLexico("Nao utilize pipe que nao seja ||", getLineNumber());
			}

			// Pega multiplicacao e potencia
		case Tag.ASTERISK: 		// *
			if (getCharValue(getNextChar(false)) == Tag.ASTERISK) {
				wordRead = wordRead + getNextChar(true);
				if (isPalavraReservada(wordRead)) {
					return adicionaTabelaSimbolos(palReservada.get(wordRead));
				}
			}
			else {
				if (isPalavraReservada(wordRead)) {
					return adicionaTabelaSimbolos(palReservada.get(wordRead));
				}
			}
			// Pega a adicao
		case Tag.PLUS_SIG: 		// +
			// Pega a subtracao
		case Tag.MINUS_SIG: 	// -
			// Pea a divisao
		case Tag.DIV_SIG: 		// /
			if (isPalavraReservada(wordRead)) {
				return adicionaTabelaSimbolos(palReservada.get(wordRead));
			}
			// Pega a comparacao com < ou <= ou <>
		case Tag.MINOR: 		// <
			if (getCharValue(getNextChar(false)) == Tag.GREATER) {
				wordRead = wordRead + getNextChar(true);
				if (isPalavraReservada(wordRead)) {
					return adicionaTabelaSimbolos(palReservada.get(wordRead));
				}
			}
			// Pega a comparacao com > ou >=
		case Tag.GREATER: 		// >
			// Pega atribuicao de valor ou comparacao ==
		case Tag.EQUAL_SIG: 	// =
			if (getCharValue(getNextChar(false)) == Tag.EQUAL_SIG) {
				wordRead = wordRead + getNextChar(true);
				if (isPalavraReservada(wordRead)) {
					return adicionaTabelaSimbolos(palReservada.get(wordRead));
				}
			}
			else {
				return adicionaTabelaSimbolos(new ReservedWord(chrRead,getCharValue(chrRead)));
			}
		case Tag.ASPASDUPLA:
			if (getCharValue(getNextChar(false)) != Tag.ASPASDUPLA) {
				while (getCharValue(getNextChar(false)) != Tag.ASPASDUPLA && !isEOF(chrRead)) {
					wordRead = wordRead + getNextChar(true);
				}
				wordRead = wordRead + getNextChar(true);
			}
			else {
				wordRead = wordRead + getNextChar(true);
			}
			// pega so a primeira letra
			if (wordRead.length() >= 3) {
				wordRead = wordRead.substring(1, 2);
			}
			
			return adicionaTabelaSimbolos(new ReservedWord(wordRead, Tag.CHAR));
		}
			
// verifica os numeros
		if (isDigit(chrRead)) {
			wordRead = "";
			do {
				wordRead = wordRead + chrRead;
				chrRead = isDigit(getNextChar(false)) ? getNextChar(true) : getNextChar(false);
			} while (isDigit(chrRead) && !isEOF(chrRead));
			if (getCharValue(chrRead) == Tag.POINT) {
				wordRead = wordRead + chrRead;
				deleteNextChar();// deletando o ponto
				chrRead = getNextChar(false);
				if (isDigit(chrRead)) {
					deleteNextChar();
					do {
						wordRead = wordRead + chrRead;
						chrRead = isDigit(getNextChar(false)) ? getNextChar(true) : getNextChar(false);
					} while (isDigit(chrRead) && !isEOF(chrRead));
				}
			}
			if (isLetter(getNextChar(false))) {
				new ErroLexico("Nao pode colocar letras depois de numeros", getLineNumber());
			}
			if (wordRead.contains(".")) { 
				new ErroLexico("Nao use float point, ok?", getLineNumber());
				return adicionaTabelaSimbolos(new ReservedWord(wordRead, Tag.REAL));
			}
			else { 
				return adicionaTabelaSimbolos(new ReservedWord(wordRead, Tag.NUMBER));
			}
		}

// verificar se eh palavra
		else if (isLetter(chrRead)) {
			wordRead = "";
			do {
				wordRead = wordRead + chrRead;
				chrRead = ( isValidIdCharacter(getNextChar(false)) )
							? getNextChar(true) : getNextChar(false);
			} while ( isValidIdCharacter(chrRead)  && !isEOF(chrRead));
			
			if (isPalavraReservada(wordRead))
				return adicionaTabelaSimbolos(palReservada.get(wordRead));
			else 
				return adicionaTabelaSimbolos(new ReservedWord(wordRead, Tag.ID));
				
		}
		
// verifica se é EOF
		else if (isEOF(chrRead)) {
			return new Token("\0",Tag.EOF);
		}
		
		return new Token("ERROR!",Tag.ERROR);
	}

	/**
	 * Funcao que verifica qual o proximo token, e coloca de volta no lugar
	 * @return Token
	 */
	public Token peekNextToken() {
		Token nextToken = null;
		// Pega o proximo Token
		nextToken = getNextToken();
		// Colocando o Token de volta no programa
		StringBuffer str_buf = new StringBuffer(nextToken.lexeme);
		str_buf.append(program);
		program = str_buf;
		// Retornando o proximo token
		return nextToken;
	}
	
	public boolean isEOF(String value) {
		int chr = getCharValue(value);
		if (chr == Tag.EOF)
			return true;
		return false;
	}

	public boolean isValidIdCharacter(String value) {
		int chr = getCharValue(value);
		if (isLetter(value) || 
				isDigit(value) ||
				chr == Tag.UNDERLINE)
			return true;
		return false;
	}

	private static int getCharValue (String value) {
		return (int) value.charAt(0);
	}
	
	public boolean isDigit (String value) {
		int chr = getCharValue(value);
		if (48 <= chr && chr <= 57) // numeros 0-9
			return true;
		return false;
	}

	public boolean isLetter (String value) {
		int chr = getCharValue(value);
		if ((65 <= chr && chr <= 90) || // letras maiusculas
				(97 <= chr && chr <= 122) || // letras minusculas
				chr == Tag.UNDERLINE) // underline, caso
			return true;
		return false;
	}
	
	public boolean isWhitespace (String value) {
		int chr = getCharValue(value);
		if (chr == Tag.TAB || // \t
				chr == Tag.NEWLINE || // \n
				chr == Tag.SPACE) {// <SPACE>
			return true;
		}
		return false;
	}

	public boolean isPalavraReservada (String value){
		if (palReservada.containsKey(value))
				return true;
		return false;
	}

	/**
	 * @param lineNumber the lineNumber to set
	 */
	public void setLineNumber(int lineNumber) {
		this.lineNumber = lineNumber;
	}

	/**
	 * increase the lineNumber by 1
	 * @param lineNumber the lineNumber to set
	 */
	public void addLineNumber() {
		this.lineNumber++;
	}

	/**
	 * @return the lineNumber
	 */
	public int getLineNumber() {
		return lineNumber;
	}

	public static boolean isOperador(String value) {
		int chr = getCharValue(value);
		if (chr == Tag.ASTERISK || 
				chr == Tag.DIV ||
				chr == Tag.ADD ||
				chr == Tag.MINUS ||
				chr == Tag.POW) // Operadores
			return true;
		return false;
	}

}
